
package com.phevos.web.cas.client.filter;

import java.io.IOException;
import java.net.URLEncoder;
import java.util.StringTokenizer;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.xml.parsers.ParserConfigurationException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

import com.phevos.web.cas.client.ProxyTicketValidator;
import com.phevos.web.cas.client.Util;

public class CASFilter implements Filter
{

    private static final Logger logger = LoggerFactory.getLogger(CASFilter.class.getName());
    // *********************************************************************
    // Constants

    /** Session attribute in which the username is stored */
    public final static String CAS_FILTER_USER = "edu.yale.its.tp.cas.client.filter.user";

    public final static String CAS_FILTER_FLAG = "edu.yale.its.tp.cas.client.filter.flag";

    public final static String CAS_FILTER_TICKET = "cas_ticket";

    // *********************************************************************
    // Configuration state

    private String casLogin, casValidate, casAuthorizedProxy, casServiceUrl, casRenew;

    private boolean wrapRequest;

    /**
     * Initialization
     * 
     * @param config FilterConfig
     * @throws ServletException
     */
    public void init(FilterConfig config) throws ServletException
    {
        casLogin = config.getInitParameter("edu.yale.its.tp.cas.client.filter.loginUrl");
        casValidate = config.getInitParameter("edu.yale.its.tp.cas.client.filter.validateUrl");
        casServiceUrl = config.getInitParameter("edu.yale.its.tp.cas.client.filter.serviceUrl");
        casAuthorizedProxy = config.getInitParameter("edu.yale.its.tp.cas.client.filter.authorizedProxy");
        casRenew = config.getInitParameter("edu.yale.its.tp.cas.client.filter.renew");
        // casServerName =
        // config.getInitParameter(
        // "edu.yale.its.tp.cas.client.filter.serverName");
        wrapRequest = Boolean.valueOf(config.getInitParameter("edu.yale.its.tp.cas.client.filter.wrapRequest")).booleanValue();
    }

    /**
     * Filter processing
     * 
     * @param request ServletRequest
     * @param response ServletResponse
     * @param fc FilterChain
     * @throws ServletException
     * @throws IOException
     */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain fc) throws ServletException, IOException
    {

        // make sure we've got an HTTP request
        if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse))
            throw new ServletException("CASFilter protects only HTTP resources");
        // Wrap the request if desired
        if (wrapRequest)
        {
            request = new CASFilterRequestWrapper((HttpServletRequest)request);
        }

        HttpSession session = ((HttpServletRequest)request).getSession();

        // ���webappl��l��ʹ��
        String flag = request.getParameter(CAS_FILTER_FLAG);
        String userid = request.getParameter(CAS_FILTER_USER);
        
        if(logger.isDebugEnabled())
        {
            logger.debug("flag is"+flag);
            logger.debug("userid is"+userid);
        }
        
        if (session != null && userid != null && !userid.trim().equals(""))
        {
            if (flag == null || flag.equals("") || flag.equals("1"))
            {
                userid = decode(userid);
            }
            session.setAttribute(CAS_FILTER_USER, userid);
        }
        String ticket = request.getParameter("ticket");
        
        if(logger.isDebugEnabled())
        {
            logger.debug("ticket is"+ticket);
        }
        // if our attribute's already present, don't do anything
        if (session != null && session.getAttribute(CAS_FILTER_USER) != null)
        {
            if(logger.isDebugEnabled())
            {
                logger.debug("CAS_FILTER_USER:"+session.getAttribute(CAS_FILTER_USER));
            }
            if (ticket != null)
            {
                ((HttpServletResponse)response).sendRedirect(Util.getRedirect((HttpServletRequest)request));
                return;
            }
            // �ж��Ƿ�Ϊ��תָ��,�����,���ټ���
            if (redirectApp((HttpServletRequest)request, (HttpServletResponse)response))
            {
                return;
            }

            fc.doFilter(request, response);
            return;
        }

        // otherwise, we need to authenticate via CAS

        // no ticket? abort request processing and redirect
        if (ticket == null || ticket.equals(""))
        {
            if (casLogin == null)
            {
                throw new ServletException("When CASFilter protects pages that do not receive a 'ticket' "
                        + "parameter, it needs a edu.yale.its.tp.cas.client.filter.loginUrl " + "filter parameter");
            }
            ((HttpServletResponse)response).sendRedirect(casLogin + "?service=" + getService((HttpServletRequest)request)
                    + ((casRenew != null && !casRenew.equals("")) ? "&renew=" + casRenew : ""));

            // abort chain
            return;
        }

        // Yay, ticket! Validate it.
        try
        {
            String user = getAuthenticatedUser((HttpServletRequest)request);
            if (user == null)
            {
                throw new ServletException("Unexpected CAS authenticationerror");
            }
            if (session != null)
            {// probably unncessary
                session.setAttribute(CAS_FILTER_USER, user);
                session.setAttribute(CAS_FILTER_TICKET, ticket);
            }
        }
        catch (ServletException e)
        {

            ((HttpServletResponse)response).sendRedirect(casLogin + "?service=" + getService((HttpServletRequest)request)
                    + ((casRenew != null && !casRenew.equals("")) ? "&renew=" + casRenew : ""));
            return;
        }
        // �����֤ʧ��,�����·�����֤����

        // Store the authenticated user in the session

        if (ticket != null)
        {
            ((HttpServletResponse)response).sendRedirect(Util.getRedirect((HttpServletRequest)request));
            return;
        }
        // System.out.println(((HttpServletRequest) request).getRequestURI());
        // ((HttpServletResponse) response)
        // .sendRedirect(getService((HttpServletRequest) request));
        // continue processing the request
        fc.doFilter(request, response);
    }

    /**
     * �ж��Ƿ���ϵͳ�����ת,Ϊ�˲���������ļ�,����url������casredirect.jsp��β(ʵ�ʲ��޴��ļ�),���Ұ����redirect, ����redirectָ��Ҫ��ת����ϵͳ����ҪĿ����Ϊ���������׷����ȷ��ticket
     * 
     * @param request
     * @param response
     * @return
     * @throws IOException
     */
    private boolean redirectApp(HttpServletRequest request, HttpServletResponse response) throws IOException
    {
        String uri = request.getRequestURI();
        if(logger.isDebugEnabled())
        {
            logger.debug("uri is"+uri);
        }
        
        if (uri != null && uri.endsWith("casredirect.jsp"))
        {
            String redirect = request.getParameter("redirect");
            request.getSession().removeAttribute(CAS_FILTER_USER);
            response.setHeader("Pragma", "No-cache");
            response.setHeader("Cache-Control", "no-cache");
            response.setDateHeader("Expires", 0);
            response.sendRedirect(redirect + "?ticket=" + request.getSession().getAttribute(CAS_FILTER_TICKET));
            return true;
        }
        return false;

    }

    /**
     * Destruction
     */
    public void destroy()
    {
    }

    // *********************************************************************
    // Utility methods

    /**
     * Converts a ticket parameter to a username, taking into account an optionally configured trusted proxy in the tier immediately in
     * front of us.
     */
    private String getAuthenticatedUser(HttpServletRequest request) throws ServletException
    {
        ProxyTicketValidator pv = null;
        try
        {
            pv = new ProxyTicketValidator();
            pv.setCasValidateUrl(casValidate);
            pv.setServiceTicket(request.getParameter("ticket"));
            pv.setService(getService(request));
            pv.setRenew(Boolean.valueOf(casRenew).booleanValue());
            pv.validate();
            if (!pv.isAuthenticationSuccesful())
                throw new ServletException("CAS authentication error: " + pv.getErrorCode() + ": " + pv.getErrorMessage());
            if (pv.getProxyList().size() != 0)
            {
                // ticket was proxied
                if (casAuthorizedProxy == null)
                {
                    throw new ServletException("this page does not accept proxied tickets");
                }
                else
                {
                    boolean authorized = false;
                    String proxy = (String)pv.getProxyList().get(0);
                    StringTokenizer casProxies = new StringTokenizer(casAuthorizedProxy);
                    while (casProxies.hasMoreTokens())
                    {
                        if (proxy.equals(casProxies.nextToken()))
                        {
                            authorized = true;
                            break;
                        }
                    }
                    if (!authorized)
                    {
                        throw new ServletException("unauthorized top-level proxy: '" + pv.getProxyList().get(0) + "'");
                    }
                }
            }
            return pv.getUser();
        }
        catch (SAXException ex)
        {
            String xmlResponse = "";
            if (pv != null)
                xmlResponse = pv.getResponse();
            throw new ServletException(ex + " " + xmlResponse);
        }
        catch (ParserConfigurationException ex)
        {
            throw new ServletException(ex);
        }
        catch (IOException ex)
        {
            throw new ServletException(ex);
        }
    }

    /**
     * Returns either the configured service or figures it out for the current request. The returned service is URL-encoded.
     */
    private String getService(HttpServletRequest request) throws ServletException
    {
        // ensure we have a server name or service name
        // if (casServerName == null && casServiceUrl == null)
        // throw new ServletException(
        // "need one of the following configuration "
        // + "parameters: edu.yale.its.tp.cas.client.filter.serviceUrl or "
        // + "edu.yale.its.tp.cas.client.filter.serverName");

        // use the given string if it's provided
        if (casServiceUrl != null)
            return URLEncoder.encode(casServiceUrl);
        else
            // otherwise, return our best guess at the service
            return Util.getService(request);
    }

    private static String decode(String str)
    {
        if (str == null || str.trim().equals(""))
        {
            return str;
        }
        byte[] bytes = str.getBytes();
        for (int i = 0; bytes != null && i < bytes.length; i++)
        {
            byte tmp = bytes[i];
            tmp = (byte)(tmp - 1);
            bytes[i] = tmp;
        }
        return new String(bytes);
    }

}
